import { BatchedMesh, BufferGeometry, type Camera, Mesh, type Object3D, Raycaster, Vector2 } from 'three/webgpu';
import {
  acceleratedRaycast,
  computeBatchedBoundsTree,
  computeBoundsTree,
  disposeBatchedBoundsTree,
  disposeBoundsTree,
} from 'three-mesh-bvh';

import type { Editor } from './Editor';

BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;
BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree;
Mesh.prototype.raycast = acceleratedRaycast;
Raycaster.prototype.firstHitOnly = true;

const mouse = new Vector2();
const raycaster = new Raycaster();

class Selector {
  editor: Editor;
  public signals: typeof Editor.prototype.signals;
  constructor(editor: Editor) {
    const signals = editor.signals;

    this.editor = editor;
    this.signals = signals;

    // signals
    signals.intersectionsDetected.add((intersects) => {
      if (intersects.length > 0) {
        const object = intersects[0].object;
        if (object.userData.object !== undefined) {
          // helper
          this.select(object.userData.object);
        } else {
          this.select(object);
        }
      } else {
        this.select(null);
      }
    });
  }

  getIntersects(raycaster: Raycaster) {
    const objects: Object3D[] = [];

    this.editor.scene.traverseVisible((child) => {
      objects.push(child);
    });

    this.editor.sceneHelpers.traverseVisible((child) => {
      if (child.name === 'picker') objects.push(child);
    });

    return raycaster.intersectObjects(objects, false);
  }

  getPointerIntersects(point: Vector2, camera: Camera) {
    mouse.set(point.x * 2 - 1, -(point.y * 2) + 1);
    raycaster.setFromCamera(mouse, camera);
    return this.getIntersects(raycaster);
  }

  select(object: Object3D | null) {
    if (this.editor.selected === object) return;

    const uuid = object?.uuid || '';

    this.editor.selected = object;
    this.editor.config.setKey('selected', uuid);

    this.signals.objectSelected.dispatch(object);
  }

  deselect() {
    this.select(null);
  }
}

export { Selector };
